/*
 * Licensed to the Tai Ping Jin Ke
 *
 * Copyright (c) 2022 .
 * All rights reserved.
 * 项目名称：呼叫中心-网关层
 * 版权说明：本软件属太平金融科技服务(上海)有限公司所有，在未获得太平金融科技服务(上海)有限公司正式授权情况下，任何企业和个人，不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。
 */
package com.taiping.cc.gateway.security.config;

import com.taiping.cc.gateway.security.handler.DefaultAuthorizationManager;
import com.taiping.cc.gateway.security.handler.SessionAuthenticationManager;
import com.taiping.cc.gateway.security.handler.DefaultSecurityContextRepository;
import com.taiping.cc.gateway.security.handler.DefaultAuthenticationEntryPoint;
import com.taiping.cc.gateway.security.handler.DefaultAccessDeniedHandler;
import com.taipingframework.boot.dlt.filters.DltFilter;
import com.taipingframework.boot.reactor.session.filters.WebSessionFilter;
import com.taipingframework.boot.web.reactor.WebFluxFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.DelegatingReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import reactor.core.publisher.Mono;

import java.util.LinkedList;
import java.util.List;

/**
 * webflux spring-security 核心配置
 * 相关参考：
 * https://blog.csdn.net/crazy_thinking1/article/details/130464225
 */
@EnableWebFluxSecurity
@RequiredArgsConstructor
public class SpringSecurityConfig {

    private final DefaultAuthorizationManager defaultAuthorizationManager;
    private final SessionAuthenticationManager sessionAuthenticationManager;
    private final DefaultSecurityContextRepository defaultSecurityContextRepository;
    private final DefaultAuthenticationEntryPoint defaultAuthenticationEntryPoint;
    private final DefaultAccessDeniedHandler defaultAccessDeniedHandler;
    private final CustomizeSecurityProperties properties;

    /**
     * spring-security 权限设置过滤器
     */
    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {
        // 白名单
        List<String> whiteList = properties.getWhiteList();
        // 需要认证的名单
        List<String> checkList = properties.getNeedCheck();
        // 需要鉴权的名单
        List<String> needAuthentication = properties.getNeedAuthentication();

        // RedirectServerLogoutSuccessHandler redirectServerLogoutSuccessHandler = new RedirectServerLogoutSuccessHandler();
        // redirectServerLogoutSuccessHandler.setLogoutSuccessUrl(URI.create("/logoutSuccess"));

        httpSecurity
                .httpBasic().disable()
                // 将自定义Filter的bean对象添加到过滤器链中的***过滤器之前
                // 通过 addFilterBefore 配置，可以将我们处理之后的 HTTP请求头参数 在spring-security的上下文中使用
                // 注意：
                // - 此处添加的全局过滤器，不要托管给Spring容器打理，否则每个WebFilter都会多执行一次。
                // - 在配置文件中已经禁用了这三个过滤器
                .addFilterAt(new WebFluxFilter(), SecurityWebFiltersOrder.FIRST)
                .addFilterAfter(new WebSessionFilter(), SecurityWebFiltersOrder.FIRST)
                .addFilterBefore(new DltFilter(), SecurityWebFiltersOrder.HTTP_HEADERS_WRITER)
                /*.oauth2Login()
                .and().oauth2Client()
                .and().oauth2ResourceServer()
                .and()*/
                /*.formLogin()
                // 注意该uri必须使用Post访问，且content-type必须为application/x-www-form-urlencoded
                .requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/authenticating/form"))
                .authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/authenticatingSucceed"))
                .authenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/authenticatingFailed"))
                .and().logout().logoutSuccessHandler(redirectServerLogoutSuccessHandler)
                .and()*/
                // 配置自定义处理器
                .authenticationManager(reactiveAuthenticationManager())
                // 配置获取上下文信息的类
                .securityContextRepository(defaultSecurityContextRepository)
                // 配置需要鉴权的请求拦截处理
                .authorizeExchange()
                .pathMatchers(whiteList.toArray(new String[0])).permitAll()
                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                .pathMatchers(needAuthentication.toArray(new String[0])).access(defaultAuthorizationManager)
                .pathMatchers(checkList.toArray(new String[0])).authenticated()
                // 此处是签权失败才会执行的
                .and().exceptionHandling().accessDeniedHandler(defaultAccessDeniedHandler)
                // 这个是没有认证才会执行的
                .and().exceptionHandling().authenticationEntryPoint(defaultAuthenticationEntryPoint)
                // CSRF(cross-site request forgery)跨站请求伪造；CORS(Cross-Origin Resource Sharing)跨域资源共享
                .and().csrf().disable();

        return httpSecurity.build();
    }

    /**
     * 注册用户信息验证管理器，可按需求添加多个，按顺序执行
     */
    @Bean
    public ReactiveAuthenticationManager reactiveAuthenticationManager() {
        LinkedList<ReactiveAuthenticationManager> managers = new LinkedList<>();
        managers.add(authentication -> {
            // 其它登录方式（比如手机号+验证码方式登录），可在此设置不得抛出异常或者Mono.error
            return Mono.empty();
        });
        // 必须放最后不然会优先使用用户名+密码校验，但是用户名密码不对时，此AuthenticationManager会调用Mono.error造成后面的AuthenticationManager不生效
        managers.add(sessionAuthenticationManager);
        return new DelegatingReactiveAuthenticationManager(managers);
    }

}
